import java.util.*;

class Principal {      
    private static final long tempoMaximo = 10000, tempoMinimo = 5;
    private static final int repeticions = 1000, anchura = 18;
    private static boolean bucle = false;
    private static Crono crono = new Crono();
    private static int filas;
    private static int nInicial = 500;
    private static int salto = 2;
    
    private static void imprimeLina(int i) {
        for (int j=0; j < i; j++) System.out.print("-");
        System.out.println();
    }

    /**
     A funcion 'avaliar' devolve un array de 'double' que conten os tempos empregados polo algoritmo
     de ordeacion para ordear distintas cantidades de elementos (125,250,500,...). A disposicion
     inicial dos elementos dentro do array ven dada polo parametro 'ini'.

     NOTA: Os tempos que precisen empregar un bucle apareceran como negativos, para diferencialos
     dos tempos que non o precisan

    **/


    private static double [] avaliar(Ordenacion orde, InicializacionDeVectores ini) {
        
	filas = 6; // En principio, faranse 'filas' iteracions (se non se supera o tempo maximo)

        double [] tempo = new double [12];
        Entero [] v;
        
        for (int n=nInicial, indice=0; indice < filas; n*=salto, indice++){
            v = new Entero[n];
            ini.inicializar(v);           

            //* Tempos para ORDEACION *//
            crono.iniciar();
            orde.ordenar(v);
            tempo[indice] = crono.tiempo();
            
            //* ORDEACION para tempos menores a 'tempoMinimo' *//
            if (tempo[indice] < tempoMinimo) {
                
                crono.iniciar();
                for (int j=0; j<repeticions; j++){
                    ini.inicializar(v);
                    orde.ordenar(v);
                }
                long aux1 = crono.tiempo();
                
                crono.iniciar();
                for (int j=0; j<repeticions; j++){
                    ini.inicializar(v);
                }
                long aux2 = crono.tiempo();
                tempo[indice] = -(aux1 - aux2)/(double)repeticions;
            }

            if (Math.abs(tempo[indice]) > tempoMaximo) {
                filas = indice;
                break;
            }
        }

        return (tempo);
    }    
    /**
     A funcion 'analise' encargase de avalia-las complexidades de cada unha das ordenacions, en distintos
     casos: o mellor(ascendente), o intermedio(aleatoria) e o peor(descendente).
     Para elo recibe o tipo de ordeacion('orde'), o tipo de inicializacion('ini'), o array de tempos obtidos
     coa funcion 'avaliar' (tempo) e un umbral (no caso de tratarse da Ordeacion Rapida, noutro caso o valor
     de 'umbral' sera ignorado)
     **/


    private static void analise(Ordenacion orde, InicializacionDeVectores ini, double [] tempo, int umbral){
        
        boolean eInsercion = orde instanceof OrdenacionPorInsercion;
        boolean eAleatoria = ini instanceof Aleatoria;
        boolean eAscendente = ini instanceof Ascendente;
        boolean eDescendente = ini instanceof Descendente;
        
        System.out.println();
        System.out.println("\nAlgoritmo de " + orde.toString() + " con " + ini.toString() +(!eInsercion?(".Umbral: "+umbral):"") + "\n");
        System.out.print(Format.format("n",anchura-10,true));
        System.out.print(Format.format("T(n)",anchura-5,false));
        
        if (eInsercion) // Ordeacion por Insercion
            if (eAscendente){   // Ascendente 
		System.out.print(Format.format("T(n)/n^0.5",anchura,false));
                System.out.print(Format.format("T(n)/n^0.8",anchura,false));
                System.out.print(Format.format("T(n)/n",anchura,false));
                System.out.print(Format.format("T(n)/n^1.2",anchura,false));
		System.out.println(Format.format("T(n)/n^1.5",anchura,false));
            }
            else {              // Descendente || Aleatoria
		System.out.print(Format.format("T(n)/n^1.5",anchura,false));
                System.out.print(Format.format("T(n)/n^1.8",anchura,false));
                System.out.print(Format.format("T(n)/n^2",anchura,false));
                System.out.print(Format.format("T(n)/n^2.2",anchura,false));
		System.out.println(Format.format("T(n)/n^2.5",anchura,false));
            }
        else {             // Ordeacion Rapida
            System.out.print(Format.format("T(n)/n",anchura,false));
            System.out.print(Format.format("T(n)/(n*ln(n))",anchura,false));
            System.out.println(Format.format("T(n)/n^1.5",anchura,false));
        }
            
        imprimeLina(7*anchura - 15);

        for (int n=nInicial, indice=0; indice < filas; n*=salto, indice++) {         
            System.out.print(Format.format(String.valueOf(n),anchura-10,true));
            System.out.print(Format.format(Math.abs(tempo[indice]),anchura-6,anchura-14)+((tempo[indice] <= 0)?'*':' '));
            
            tempo[indice] = Math.abs(tempo[indice]);
            
            if (eInsercion) // Ordeacion por Insercion
                if (eAscendente){   // Ascendente
		    System.out.print(Format.format(tempo[indice]/Math.pow(n,0.5),anchura,anchura-7));
                    System.out.print(Format.format(tempo[indice]/Math.pow(n,0.8),anchura,anchura-7));
                    System.out.print(Format.format(tempo[indice]/n,anchura,anchura-7));
                    System.out.print(Format.format(tempo[indice]/Math.pow(n,1.2),anchura,anchura-7));
		    System.out.println(Format.format(tempo[indice]/Math.pow(n,1.5),anchura,anchura-7));
                }
                else {              // Descendente || Aleatoria
		    System.out.print(Format.format(tempo[indice]/Math.pow(n,1.5),anchura,anchura-7));	
                    System.out.print(Format.format(tempo[indice]/Math.pow(n,1.8),anchura,anchura-7));
                    System.out.print(Format.format(tempo[indice]/Math.pow(n,2),anchura,anchura-7));
                    System.out.print(Format.format(tempo[indice]/Math.pow(n,2.2),anchura,anchura-7));
		    System.out.println(Format.format(tempo[indice]/Math.pow(n,2.5),anchura,anchura-7));
                }
            else {            // Ordeacion Rapida
                System.out.print(Format.format(tempo[indice]/n,anchura,anchura-7));
                System.out.print(Format.format(tempo[indice]/(n*Math.log(n)),anchura,anchura-7));;
                System.out.println(Format.format(tempo[indice]/Math.pow(n,1.5),anchura,anchura-7));
            }
        }
        imprimeLina(7*anchura - 15);
        System.out.println ("(*) Empregouse un bucle de "+ repeticions + " iteracions. O tempo reflectido corresponde a unha iteracion");
        System.out.println("Todolos tempos en milisegundos");
    }
    
    public static void main (String [] args){
        Ordenacion ordeacion;
        Ascendente iniAscend = new Ascendente();
        Aleatoria iniAleat = new Aleatoria();
        Descendente iniDescend = new Descendente();        
        double [] tempo;
        ordeacion = new OrdenacionPorInsercion();
        
        analise(ordeacion, iniAscend, avaliar(ordeacion, iniAscend),0);
        analise(ordeacion, iniAleat, avaliar(ordeacion, iniAleat),0);
        analise(ordeacion, iniDescend, avaliar(ordeacion, iniDescend),0);
        
        System.out.println("Os algoritmos executaronse sobre unha arquitectura "+ System.getProperty("os.arch")  + " baixo o S.O. " + System.getProperty("os.name") + " version "
                           + System.getProperty("os.version") + ".\nCoa version " + System.getProperty("java.version") + " de JAVA\n");
        System.out.println("Data: "+ (new Date()).toString());
    }
}
